#include "memory.h"
#include <assert.h>


/* ****************************************************************** */

static t_alloc_entry *alloc_head, *alloc_tail;

/* TODO: now just use a sequential bi-directional chains to record alloc
 * entries. It's a low-performance method and need to improved. */
/* FIXME: need to be thread-safe */
int add_alloc_entry(void *p, size_t size)
{
	t_alloc_entry *entry;
	
	entry = libc_malloc(sizeof(t_alloc_entry));
	if (!entry) {
		perr("failed to alloc entry!\n");
		return -1;
	}

	entry->ptr = p;
	entry->size = size;
	entry->pid = getpid();
	entry->prev = NULL;
	entry->next = NULL;
	if (alloc_tail) {
		/* append to tail */
		alloc_tail->next = entry;
		entry->prev = alloc_tail;
		alloc_tail = entry;
	} else {
		alloc_head = alloc_tail = entry;
	}
}

void del_alloc_entry(void *p)
{
	t_alloc_entry *entry = alloc_head;

	while (entry) {
		if (entry->ptr == p) {
			if (entry->prev)
				entry->prev->next = entry->next;
			if (entry->next)
				entry->next->prev = entry->prev;
			if (entry == alloc_head) {
				alloc_head = entry->next;
			}
			if (entry == alloc_tail) {
				alloc_tail = entry->prev;
			}

			libc_free(entry);
			return;
		}

		entry = entry->next;
	}

	perr("free %p not found!\n", p);
}

void show_alloc_entry(void)
{
	t_alloc_entry *entry = alloc_head;

	while (entry) {
		plog("[%d] memory leak: %p, %d\n", entry->pid, entry->ptr, entry->size);
		entry = entry->next;
	}
}

/* ****************************************************************** */

/* override the libc functions */
/*
void __assert_fail (__const char *__assertion,
		__const char *__file,
		unsigned int __line,
		__const char *__function)
{
	perr("%s: %s[%d] %s()\n", __assertion, __file, __line, __function);
}
*/

void* malloc(size_t size)
{
	void *result;

	result = libc_malloc(size);

	if (result)
		add_alloc_entry(result, size);

	pdbg("another_malloc: %p, %d\n", result, size);

	return result;
}

void* calloc(size_t nmemb, size_t size)
{
	void *result;

	result = libc_calloc(nmemb, size);

	pdbg("another_calloc: %p, %d, %d\n", result, nmemb, size);

	return result;
}

void* realloc(void *ptr, size_t size)
{
	void *result;

	result = libc_realloc(ptr, size);

	pdbg("another_realloc: %p, %p, %d\n", result, ptr, size);

	return libc_realloc(ptr, size);
}

void free(void *ptr)
{
	pdbg("another_free: %p\n", ptr);

	if (ptr) {
		del_alloc_entry(ptr);
	}

	return libc_free(ptr);
}

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)
{
	pdbg("another_mmap: %p, %d, %d, %d, %d, %d\n",
		addr, length, prot, flags, fd, offset);

	return libc_mmap(addr, length, prot, flags, fd, offset);
}

void* mremap(void *old_address, size_t old_size, size_t new_size, int flags, ...)
{
	pdbg("another_mremap: %p, %d, %d, %d\n",
		old_address, old_size, new_size, flags);

	return libc_mremap(old_address, old_size, new_size, flags);
}

int munmap(void *addr, size_t length)
{
	pdbg("another_mmap: %p, %d\n", addr, length);

	return libc_munmap(addr, length);
}

int posix_memalign(void **memptr, size_t alignment, size_t size)
{
	pdbg("another_posix_memalign: %p, %d, %d\n", memptr, alignment, size);

	return libc_posix_memalign(memptr, alignment, size);
}

void *memalign(size_t boundary, size_t size)
{
	pdbg("another_memalign: %d, %d\n", boundary, size);

	return libc_memalign(boundary, size);
}

/* ****************************************************************** */

void memory_begin(void)
{
	plog("#### memory begin!\n");
}

void memory_dump(void)
{
	plog("#### memory dump!\n");

	show_alloc_entry();
}

void memory_end(void)
{
	t_alloc_entry *entry = alloc_head;
	t_alloc_entry *next;

	plog("#### memory end!\n");

	while (entry) {
		next = entry->next;
		libc_free(entry);
		entry = next;
	}
	alloc_head = alloc_tail = NULL;
}

/* ****************************************************************** */
